#include "utils/bundle-include.hpp"
using namespace cas;

// global options
std::string optTrace = "";         /**< trace file path */
uint32_t cacheSizeArg = 16 * 1024; /**< 16K -> 8MB */

// global vars
TraceFiu trace;

const int32_t coeff = 500;

struct OptItem
{
    CachePage page;
    uint64_t occ; /**< next occurence */

    friend inline bool operator<(const OptItem &lhs, const OptItem &rhs)
    {
        // get first eviction page
        return lhs.occ > rhs.occ || !(lhs.page < rhs.page);
    }
};

CacheEvictionStatistic optStat;

static inline void print_hit_ratio(const CacheEvictionStatistic &stat)
{
    printf("[opt] %ld, hit: %lf (%ld / %ld)\n",
           stat.requestNumber,
           stat.getHitRatio(),
           stat.requestHitSize,
           stat.requestSize);
}

size_t getNextRequest(const CachePage &page, const size_t &begin = 0, const size_t &span = 1ull << 53)
{
    const size_t traceLines = trace.dataSize();
    size_t currSpan = 0;
    for (size_t i = begin; i < traceLines; i++)
    {
        const auto &item = trace.data[i];
        if (page.lba == item.inode)
        {
            return i; // found at i
        }
        currSpan++;
        if (currSpan >= span)
        {
            return SIZE_MAX; // not found
        }
    }
    return SIZE_MAX; // not found
}

int main(int argc, char *argv[])
{
    // parsing command line
    std::string optEviction = ""; // unused, OPT here
    int32_t policy = -1;          // unused, must be 4
    CommandLine cmd;
    // cmd.AddNonOption("eviction", "Evction type", optEviction);
    cmd.AddNonOption("prefetch", "Mithril record policy", policy);
    cmd.AddNonOption("traceFile", "Path to trace file", optTrace);
    cmd.AddNonOption("cacheSize", "Number of cache nodes", cacheSizeArg);
    cmd.Parse(argc, argv);
    std::cout << "# cache size: " << cacheSizeArg << std::endl;

    SASSERT(policy == 4);

    // LogComponentEnableAll(LOG_WARN);
    // LogComponentPrintList();

    // read in trace
    trace.setPath(optTrace);
    trace.readFile();
    const size_t traceLines = trace.dataSize();
    std::cout << "# num of lines in trace: " << trace.dataSize() << std::endl;

    // opt set
    std::unordered_map<CachePage, uint64_t> opt;
    size_t optSize = cacheSizeArg;
    size_t optLookAhead = cacheSizeArg * coeff; // fore-see length
    std::cout << "# co: " << coeff << std::endl;

    // timing
    chrono_clock_t startAll;
    chrono_duration_t allTime(0);

    // run trace
    startAll = chrono_now();
    for (size_t i = 0; i < traceLines; i++)
    {
        const auto &item = trace.data[i];

        if (i % 10000 == 0)
        {
            print_hit_ratio(optStat);
        }

        optStat.requestNumber++;
        optStat.requestSize++;

        // cache eviction
        CachePage page(0, item.inode);

        if (opt.find(page) != opt.end())
        {
            // cache hit
            optStat.requestHitSize++;
            opt[page] = getNextRequest(page, i + 1, optLookAhead); // update fore-see
        }
        else
        {
            // not hit
            const auto nxt = getNextRequest(page, i + 1, optLookAhead);
            if (nxt != SIZE_MAX)
            {
                // current page will recur
                if (opt.size() < optSize)
                {
                    opt[page] = nxt; // not full , add new item
                }
                else
                {
                    // full, evict least recent recur item
                    CachePage maxPage;
                    size_t maxPageAppear = 0;
                    for (const auto &it : opt)
                    {
                        if (it.second > maxPageAppear)
                        {
                            maxPage = it.first; // mark least recent
                            maxPageAppear = it.second;
                            if (maxPageAppear == SIZE_MAX)
                            {
                                break; // not occur anymore, evict imm.
                            }
                        }
                    }
                    SASSERT(maxPageAppear > 0); // should updated
                    opt.erase(maxPage);         // add new cache item
                    opt[page] = nxt;
                }
            }
        }
    }

    // finish
    allTime = chrono_now() - startAll;
    printf("[time] all %10lf\n", allTime.count());
    print_hit_ratio(optStat);

    return 0;
}
